<!DOCTYPE html>
<html lang="en">
<head>
  <title>Scheduled Tasks - Born to build better enterprise frameworks and apps</title>
  <meta charset="utf-8">
  <meta name="description" content="index.description">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="/images/favicon.png" type="image/x-icon">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.css" />
<link rel="stylesheet" href="/css/index.css">

    <script>
    !function(t,e,a,r,c){t.TracertCmdCache=t.TracertCmdCache||[],t[c]=window[c]||
      {_isInit:!0,call:function(){t.TracertCmdCache.push(arguments)},
      start:function(t){this.call('start',t)}},t[c].l=new Date;
      var n=e.createElement(a),s=e.getElementsByTagName(a)[0];
      n.async=!0,n.src=r,s.parentNode.insertBefore(n,s)}
    (window,document,'script','https://tracert.alipay.com/tracert.js','Tracert');
      Tracert.start({
        plugins: [ 'BucName' ],
        spmAPos: 'a454',
        spmBPos: 'b4893',
      });
    </script>
  
</head>
<body>
  <div class="nav" >
  <header>
    <a href="/en/" class="nav-logo leftpadding" alt="egg"><img src="https://zos.alipayobjects.com/rmsportal/VTcUYAaoKqXyHJbLAPyF.svg"></a>
    <ul class="nav-item">
      <li>
        <form id="search-form">
          <input type="text" id="search-query" class="search-query st-default-search-input">
        </form>
      </li>
      <li><a href="/en/intro/" alt="Guide">Guide</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/en/tutorials/index.html" alt="Tutorials">Tutorials</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="Plugins">Plugins</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="Release">Release</a></li>
      
      
        <li class="translations">
          <a class="nav-link">中文文档</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/basics/schedule.html" style="color: #22ab28">English</a></li><li><a id="zh-cn" href="/zh-cn/basics/schedule.html" >中文</a></li></ul>
        </li>
      
      <li><iframe src="https://ghbtns.com/github-btn.html?user=eggjs&repo=egg&type=star&count=true" frameborder="0" scrolling="0" width="150px" height="20px"></iframe></li>
    </ul>
    <a id="mobileTrigger" href="#" class="mobile-trigger">
      <ul>
        <li></li>
        <li></li>
        <li></li>
      </ul>
    </a>
  </header>
</div>
  <div id="container" class="container">
    <div class="page-main">
  <article class="markdown-body">
    <h1>Scheduled Tasks</h1>
    <p>Although the HTTP Server we developed using the framework is a request-response model, there are still many scenarios that need to execute some scheduled tasks, for example:</p>
<ol>
<li>Regularly report server application status.</li>
<li>Regularly update the local cache from the remote interface.</li>
<li>Regularly split files, delete temporary files.</li>
</ol>
<p>The framework provides a mechanism to make the development and maintenance of scheduled tasks more elegant.</p>
<h2 id="develop-scheduled-tasks"><a class="markdown-anchor" href="#develop-scheduled-tasks">#</a> Develop Scheduled Tasks</h2>
<p>All scheduled tasks should be placed in directory <code>app/schedule</code>. Each file is an independent scheduled task that could configure the properties and the detail methods to be executed.</p>
<p>A simple example, to define a scheduled task to update the remote data to the memory cache, we can create a <code>update_cache.js</code> file in the directory 'app/schedule`</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> Subscription = <span class="built_in">require</span>(<span class="string">'egg'</span>).Subscription;</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">UpdateCache</span> <span class="keyword">extends</span> <span class="title">Subscription</span> </span>&#123;</span><br><span class="line">  <span class="comment">// using `schedule` property to set the scheduled task execution interval and other configurations</span></span><br><span class="line">  <span class="keyword">static</span> get schedule() &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      interval: <span class="string">'1m'</span>, <span class="comment">// 1 minute interval</span></span><br><span class="line">      type: <span class="string">'all'</span>, <span class="comment">// specify all `workers` need to execute</span></span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// `subscribe` is the function to be executed when the scheduled task is triggered</span></span><br><span class="line">  <span class="keyword">async</span> subscribe() &#123;</span><br><span class="line">    <span class="keyword">const</span> res = <span class="keyword">await</span> <span class="keyword">this</span>.ctx.curl(<span class="string">'http://www.api.com/cache'</span>, &#123;</span><br><span class="line">      dataType: <span class="string">'json'</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="keyword">this</span>.ctx.app.cache = res.data;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = UpdateCache;</span><br></pre></td></tr></table></figure>
<p>Can also be abbreviated as</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  schedule: &#123;</span><br><span class="line">    interval: <span class="string">'1m'</span>, <span class="comment">// 1 minute interval</span></span><br><span class="line">    type: <span class="string">'all'</span>, <span class="comment">// specify all `workers` need to execute</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="keyword">async</span> task(ctx) &#123;</span><br><span class="line">    <span class="keyword">const</span> res = <span class="keyword">await</span> ctx.curl(<span class="string">'http://www.api.com/cache'</span>, &#123;</span><br><span class="line">      dataType: <span class="string">'json'</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    ctx.app.cache = res.data;</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>This scheduled task will be executed every 1 minute on every worker process, the requested remote data will be mounted back to <code>app.cache</code>.</p>
<h3 id="task"><a class="markdown-anchor" href="#task">#</a> Task</h3>
<ul>
<li><code>task</code> or <code>subscribe</code> is compatible with <code>generator function</code> and <code>async function</code>.</li>
<li>The parameter of <code>task</code> is <code>ctx</code>, anonymous Context instance, we could call <code>service</code> and others via it.</li>
</ul>
<h3 id="schedule-mode"><a class="markdown-anchor" href="#schedule-mode">#</a> Schedule Mode</h3>
<p>Schedule tasks can specify <code>interval</code> or <code>cron</code> two different schedule modes.</p>
<h4 id="interval"><a class="markdown-anchor" href="#interval">#</a> interval</h4>
<p>Configure the scheduled tasks by <code>schedule.interval</code>, scheduled tasks will be executed every specified time interval. <code>interval</code> can be configured as</p>
<ul>
<li>Integer type, the unit is milliseconds, e.g <code>5000</code>.</li>
<li>String type, will be transformed to milliseconds by <a href="https://github.com/zeit/ms" target="_blank" rel="noopener">ms</a>, e.g <code>5s</code>.</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  schedule: &#123;</span><br><span class="line">    <span class="comment">// executed every 10 seconds</span></span><br><span class="line">    interval: <span class="string">'10s'</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h4 id="cron"><a class="markdown-anchor" href="#cron">#</a> cron</h4>
<p>Configure the scheduled tasks by <code>schedule.cron</code>, scheduled tasks will be executed at specified timing according to the cron expressions. cron expressions are parsed by <a href="https://github.com/harrisiirak/cron-parser" target="_blank" rel="noopener">cron-parser</a>.</p>
<p><strong>Note: cron-parser supports second (which don't support by linux crontab).</strong></p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">*    *    *    *    *    *</span><br><span class="line">┬    ┬    ┬    ┬    ┬    ┬</span><br><span class="line">│    │    │    │    │    |</span><br><span class="line">│    │    │    │    │    └ day of week (0 - 7) (0 or 7 is Sun)</span><br><span class="line">│    │    │    │    └───── month (1 - 12)</span><br><span class="line">│    │    │    └────────── day of month (1 - 31)</span><br><span class="line">│    │    └─────────────── hour (0 - 23)</span><br><span class="line">│    └──────────────────── minute (0 - 59)</span><br><span class="line">└───────────────────────── second (0 - 59, optional)</span><br></pre></td></tr></table></figure>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  schedule: &#123;</span><br><span class="line">    <span class="comment">// executed every three hours (zero minutes and zero seconds)</span></span><br><span class="line">    cron: <span class="string">'0 0 */3 * * *'</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h3 id="type"><a class="markdown-anchor" href="#type">#</a> Type</h3>
<p>The framework scheduled tasks support two types by default, worker and all. Both worker and all support the above two schedule modes, except when it comes time to execute, the worker who executes the scheduled tasks is different:</p>
<ul>
<li><code>worker</code> type: only one worker per machine executes this scheduled task, the worker to execute is random.</li>
<li><code>all</code> type: each worker on each machine executes this scheduled task.</li>
</ul>
<h3 id="other-parameters"><a class="markdown-anchor" href="#other-parameters">#</a> Other Parameters</h3>
<p>In addition to the parameters just introduced, scheduled task also supports these parameters:</p>
<ul>
<li><code>cronOptions</code>: configure cron time zone and so on, reference <a href="https://github.com/harrisiirak/cron-parser#options" target="_blank" rel="noopener">cron-parser</a></li>
<li><code>immediate</code>: when this parameter is set to true, this scheduled task will be executed immediately after the application is started and ready.</li>
<li><code>disable</code>: when this parameter is set to true, this scheduled task will not be executed.</li>
</ul>
<h3 id="logging"><a class="markdown-anchor" href="#logging">#</a> Logging</h3>
<p>Schedule log will be written to <code>${appInfo.root}/logs/{app_name}/egg-schedule.log</code>, but won't be logged to terminal by default, you could customize via <code>config.customLogger.scheduleLogger</code>.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.default.js</span></span><br><span class="line">config.customLogger = &#123;</span><br><span class="line">  scheduleLogger: &#123;</span><br><span class="line">    <span class="comment">// consoleLevel: 'NONE',</span></span><br><span class="line">    <span class="comment">// file: path.join(appInfo.root, 'logs', appInfo.name, 'egg-schedule.log'),</span></span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h3 id="dynamically-configure-scheduled-tasks"><a class="markdown-anchor" href="#dynamically-configure-scheduled-tasks">#</a> Dynamically Configure Scheduled Tasks</h3>
<p>Sometimes we need to determine the different environment to configure the parameters of scheduled tasks. Scheduled tasks support another development style:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">app</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    schedule: &#123;</span><br><span class="line">      interval: <span class="string">'1m'</span>,</span><br><span class="line">      type: <span class="string">'all'</span>,</span><br><span class="line">      disable: app.config.env === <span class="string">'local'</span>, <span class="comment">// not execute when local dev</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="keyword">async</span> task(ctx) &#123;</span><br><span class="line">      <span class="keyword">const</span> res = <span class="keyword">await</span> ctx.curl(<span class="string">'http://www.api.com/cache'</span>, &#123;</span><br><span class="line">        contentType: <span class="string">'json'</span>,</span><br><span class="line">      &#125;);</span><br><span class="line">      ctx.app.cache = res.data;</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="manually-execute-scheduled-tasks"><a class="markdown-anchor" href="#manually-execute-scheduled-tasks">#</a> Manually Execute Scheduled Tasks</h2>
<p>We can run a scheduled task via <code>app.runSchedule(schedulePath)</code>. <code>app.runSchedule</code> reads a scheduled task file path (either a relative path in <code>app/schedule</code> or a complete absolute path), executes the corresponding scheduled task, and returns a Promise.</p>
<p>There are some scenarios we may need to manually execute scheduled tasks, for example</p>
<ul>
<li>Executing scheduled tasks manually for more elegant unit testing of scheduled tasks.</li>
</ul>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> mm = <span class="built_in">require</span>(<span class="string">'egg-mock'</span>);</span><br><span class="line"><span class="keyword">const</span> assert = <span class="built_in">require</span>(<span class="string">'assert'</span>);</span><br><span class="line"></span><br><span class="line">it(<span class="string">'should schedule work fine'</span>, <span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> app = mm.app();</span><br><span class="line">  <span class="keyword">await</span> app.ready();</span><br><span class="line">  <span class="keyword">await</span> app.runSchedule(<span class="string">'update_cache'</span>);</span><br><span class="line">  assert(app.cache);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>
<p>When the application starts up, manually perform the scheduled tasks for system initialization, waiting for the initialization finished and then starting the application. See chapter <a href="./app-start.html">Application Startup Configuration</a>, we can implement initialization logic in <code>app.js</code>.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">app</span> =&gt;</span> &#123;</span><br><span class="line">  app.beforeStart(<span class="keyword">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="comment">// ensure the data is ready before the application starts listening port</span></span><br><span class="line">    <span class="comment">// follow-up data updates automatically by the scheduled task</span></span><br><span class="line">    <span class="keyword">await</span> app.runSchedule(<span class="string">'update_cache'</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="extend-scheduled-task-type"><a class="markdown-anchor" href="#extend-scheduled-task-type">#</a> Extend Scheduled Task Type</h2>
<p>The framework scheduled tasks only support single worker execution and all worker execution, in some cases, our services are not deployed on a single machine, one of the worker processes in the cluster may execute a scheduled task.</p>
<p>The framework does not provide this functionality directly, but developers can extend the new type of scheduled tasks themselves in the upper framework.</p>
<p>Inherit <code>agent.ScheduleStrategy</code> in <code>agent.js</code> and register it with <code>agent.schedule.use()</code>:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">agent</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="class"><span class="keyword">class</span> <span class="title">ClusterStrategy</span> <span class="keyword">extends</span> <span class="title">agent</span>.<span class="title">ScheduleStrategy</span> </span>&#123;</span><br><span class="line">    start() &#123;</span><br><span class="line">      <span class="comment">// subscribe other distributed scheduling service message, after receiving the message, allow a worker process to execute scheduled tasks</span></span><br><span class="line">      <span class="comment">// the user configures the distributed scheduling scenario in the configuration of the scheduled task</span></span><br><span class="line">      agent.mq.subscribe(schedule.scene, () =&gt; <span class="keyword">this</span>.sendOne());</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  agent.schedule.use(<span class="string">'cluster'</span>, ClusterStrategy);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p><code>ScheduleStrategy</code> base class provides:</p>
<ul>
<li><code>schedule</code> - Properties of schedule tasks, <code>disable</code> is supported by default, other configurations can be parsed by developers.</li>
<li><code>this.sendOne(...args)</code> - Notice worker to execute the task randomly, <code>args</code> will pass to <code>subscribe(...args)</code> or <code>task(ctx, ...args)</code>.</li>
<li><code>this.sendAll(...args)</code> - Notice all worker to execute the task.</li>
</ul>

  </article>
  <aside id="mobileAside" class="toc">
  <div class="mobile-menu">
    <ul>
      <li><a href="/en/intro/" alt="Guide">Guide</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/en/tutorials/index.html" alt="Tutorials">Tutorials</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="Plugins">Plugins</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="Release">Release</a></li>
      
      
        <li class="translations">
          <a class="nav-link">中文文档</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/basics/schedule.html" style="color: #22ab28">English</a></li><li><a id="zh-cn" href="/zh-cn/basics/schedule.html" >中文</a></li></ul>
        </li>
      
    </ul>
  </div>
  <dl><dt id="title-Intro" style="cursor: pointer;" class="aside-title">Guide<a id="collapse-icon-Intro" class="icon opend"></a></dt><dd id=panel-Intro><ul><li><a href="/en/intro/index.html" class="menu-link">What is Egg?</a></li><li><a href="/en/intro/egg-and-koa.html" class="menu-link">Egg and Koa</a></li><li><a href="/en/intro/quickstart.html" class="menu-link">Quick Start</a></li><li><a href="/en/tutorials/progressive.html" class="menu-link">Progressive</a></li><li><a href="/en/migration.html" class="menu-link">Migration to 2.x</a></li></ul></dd><dt id="title-Basics" style="cursor: pointer;" class="aside-title">Basis Function<a id="collapse-icon-Basics" class="icon opend"></a></dt><dd id=panel-Basics><ul><li><a href="/en/basics/structure.html" class="menu-link">Directory Structure</a></li><li><a href="/en/basics/objects.html" class="menu-link">Built-in Objects</a></li><li><a href="/en/basics/env.html" class="menu-link">Environment</a></li><li><a href="/en/basics/config.html" class="menu-link">Configuration</a></li><li><a href="/en/basics/middleware.html" class="menu-link">Middleware</a></li><li><a href="/en/basics/router.html" class="menu-link">Router</a></li><li><a href="/en/basics/controller.html" class="menu-link">Controller</a></li><li><a href="/en/basics/service.html" class="menu-link">Service</a></li><li><a href="/en/basics/plugin.html" class="menu-link">Plugin</a></li><li><a href="/en/basics/schedule.html" class="menu-link">Schedule</a></li><li><a href="/en/basics/extend.html" class="menu-link">Extend</a></li><li><a href="/en/basics/app-start.html" class="menu-link">Custom Init</a></li></ul></dd><dt id="title-Core" style="cursor: pointer;" class="aside-title">Core<a id="collapse-icon-Core" class="icon opend"></a></dt><dd id=panel-Core><ul><li><a href="/en/core/development.html" class="menu-link">Development</a></li><li><a href="/en/core/unittest.html" class="menu-link">Unit Testing</a></li><li><a href="/en/core/deployment.html" class="menu-link">Deployment</a></li><li><a href="/en/core/logger.html" class="menu-link">Logger</a></li><li><a href="/en/core/httpclient.html" class="menu-link">HttpClient</a></li><li><a href="/en/core/cookie-and-session.html" class="menu-link">Cookie and Session</a></li><li><a href="/en/core/cluster-and-ipc.html" class="menu-link">Cluster and IPC</a></li><li><a href="/en/core/view.html" class="menu-link">View</a></li><li><a href="/en/core/error-handling.html" class="menu-link">Error Handling</a></li><li><a href="/en/core/security.html" class="menu-link">Security</a></li><li><a href="/en/core/i18n.html" class="menu-link">i18n</a></li></ul></dd><dt id="title-Tutorials" style="cursor: pointer;" class="aside-title">Tutorials<a id="collapse-icon-Tutorials" class="icon opend"></a></dt><dd id=panel-Tutorials><ul><li><a href="/en/tutorials/mysql.html" class="menu-link">MySQL</a></li><li><a href="/en/tutorials/restful.html" class="menu-link">RESTful API</a></li><li><a href="/en/tutorials/passport.html" class="menu-link">Passport</a></li><li><a href="/en/tutorials/socketio.html" class="menu-link">Socket.IO</a></li><li><a href="/en/tutorials/assets.html" class="menu-link">Assets</a></li><li><a href="/en/tutorials/typescript.html" class="menu-link">TypeScript</a></li></ul></dd><dt id="title-Advanced" style="cursor: pointer;" class="aside-title">Advanced<a id="collapse-icon-Advanced" class="icon opend"></a></dt><dd id=panel-Advanced><ul><li><a href="/en/advanced/loader.html" class="menu-link">Loader</a></li><li><a href="/en/advanced/plugin.html" class="menu-link">Plugin Development</a></li><li><a href="/en/advanced/framework.html" class="menu-link">Framework</a></li><li><a href="/en/advanced/cluster-client.html" class="menu-link">Cluster Enhancement</a></li><li><a href="/en/advanced/view-plugin.html" class="menu-link">View Plugin</a></li><li><a href="/en/style-guide.html" class="menu-link">Style Guide</a></li></ul></dd><dt id="title-Community" style="cursor: pointer;" class="aside-title">Community<a id="collapse-icon-Community" class="icon opend"></a></dt><dd id=panel-Community><ul><li><a href="/en/plugins/" class="menu-link">Plugin List</a></li><li><a href="/en/contributing.html" class="menu-link">Contributing</a></li><li><a href="/en/resource.html" class="menu-link">Resource</a></li><li><a href="/en/faq.html" class="menu-link">FAQ</a></li></ul></dd></dl>
</aside>
<script>
var mobileTrigger = document.getElementById('mobileTrigger');
var mobileAside = document.getElementById('mobileAside');

var expandMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon opend';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = '';
  }
}

var collapseMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon closed';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = 'aside-panel-hidden';
  }
}

mobileAside.onclick = function(e) {
  const targetId = e.target.id;
  if (targetId && (targetId.indexOf('title-') > -1 || targetId.indexOf('collapse-icon-') > -1)) {
    const title = targetId.replace('title-', '').replace('collapse-icon-', '');
    try { 
      // the the browser may have no localStroage or JSON.parse may throw exception.
      const menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
        
      // current menu status
      const curClosed = menuInfo[title] ? menuInfo[title].closed : false; // default false

      // change UI
      curClosed ? expandMenu(title) : collapseMenu(title);

      // save menuInfo to localStorage
      menuInfo[title] = { closed: !curClosed } // opposite
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    } catch (e) {}
  }
};

mobileTrigger.onclick = function(e) {
  e.preventDefault();
  if (mobileAside.className.indexOf('mobile-show') === -1) {
    mobileAside.className += ' mobile-show';
  } else {
    mobileAside.className = 'toc';
  }
};

(function() {
  // save data to localStorage because the page will refresh when user change the url.
  let menuInfo;
  try { 
    // the the browser may have no localStroage or JSON.parse may throw exception.
    menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
    if (!menuInfo) {
      menuInfo = {};
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    }
  } catch (e) {
    menuInfo = {}; // default {}
  }

  for (const title in menuInfo) {
    if (menuInfo[title] && menuInfo[title].closed) { // menu in closed status.
      collapseMenu(title);
    } else {
      expandMenu(title);
    }
  }

  // highlight menu
  const pathname = window.location.pathname;
  const selector = `a[href="${pathname}"].menu-link,a[href="${pathname}index.html"].menu-link`;
  const menuItem = mobileAside.querySelector(selector);
  if (menuItem) { menuItem.className += ' highlight'; }
})();
</script>

</div>

  </div>
</body>
<script src="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.js"></script>
<script>
docsearch({
  apiKey: '1561de31a86f79507ea00cdb54ce647c',
  indexName: 'eggjs',
  inputSelector: '#search-query',
});
</script>
<div class="cnzz">
<script src="https://s11.cnzz.com/z_stat.php?id=1261142226&web_id=1261142226" language="JavaScript"></script>
</div>

</html>
